﻿/// Simple, really.  There is no need to initialize or even refer to TaskManager.  
/// When the first Task is created in an application, a "TaskManager" GameObject  
/// will automatically be added to the scene root with the TaskManager component  
/// attached.  This component will be responsible for dispatching all coroutines  
/// behind the scenes.  
///  
/// Task also provides an event that is triggered when the coroutine exits.  
  
using UnityEngine;  
using System.Collections;  
  
/// A Task object represents a coroutine.  Tasks can be started, paused, and stopped.  
/// It is an error to attempt to start a task that has been stopped or which has  
/// naturally terminated.  
public class Task  
{  
    /// Returns true if and only if the coroutine is running.  Paused tasks  
    /// are considered to be running.  
    public bool Running {  
        get {  
            return task.Running;  
        }  
    }  
      
    /// Returns true if and only if the coroutine is currently paused.  
    public bool Paused {  
        get {  
            return task.Paused;  
        }  
    }

    public bool Locked
    {
        get => task.Locked;
        set => task.Locked = value;
    }
      
    /// Delegate for termination subscribers.  manual is true if and only if  
    /// the coroutine was stopped with an explicit call to Stop().  
    public delegate void FinishedHandler(bool manual);  
      
    /// Termination event.  Triggered when the coroutine completes execution.  
    public event FinishedHandler Finished;  
  
    /// Creates a new Task object for the given coroutine.  
    ///  
    /// If autoStart is true (default) the task is automatically started  
    /// upon construction.  
    public Task(IEnumerator c, float killTime = 0, bool autoStart = true)  
    {  
        task = TaskManager.CreateTask(c);  
        task.Finished += TaskFinished;
        if(autoStart)  
            Start(killTime);  
    }  
      
    /// Begins execution of the coroutine  
    public void Start(float killTime = 0)  
    {  
        task.Start(killTime);  
    }  
  
    /// Discontinues execution of the coroutine at its next yield.  
    public void Stop()  
    {  
        task.Stop();
    }  
      
    public void Pause()  
    {  
        task.Pause();  
    }  
      
    public void Unpause()  
    {  
        task.Unpause();  
    }  
      
    void TaskFinished(bool manual)  
    {  
        FinishedHandler handler = Finished;  
        if(handler != null)  
            handler(manual);  
    }  
      
    TaskManager.TaskState task;  
}  
  
class TaskManager : MonoBehaviour  
{  
    public class TaskState  
    {  
        public bool Running => running;

        public bool Paused => paused;

        public bool Locked
        {
            get => locked;
            set => locked = value;
        }

        public delegate void FinishedHandler(bool manual);  
        public event FinishedHandler Finished;  
  
        IEnumerator coroutine;  
        bool running;
        bool paused; 
        bool stopped;
        private bool locked;
          
        public TaskState(IEnumerator c)  
        {  
            coroutine = c;  
        }  
          
        public void Pause()  
        {  
            paused = true;  
        }  
          
        public void Unpause()  
        {  
            paused = false;  
        }  
          
        public void Start(float killTime = 0)  
        {  
            running = true;  
            singleton.StartCoroutine(CallWrapper());
            singleton.StartCoroutine(KillTask(killTime));
        }  
          
        public void Stop()  
        {  
            if(locked)
                return;
            stopped = true;  
            running = false;
        }
          
        IEnumerator CallWrapper()  
        {
            yield return null;
            IEnumerator e = coroutine;
            while(running) {
                if(paused)
                    yield return null;
                else {
                    if(e != null && e.MoveNext()) {
                        yield return e.Current;  
                    }
                    else {
                        running = false;  
                    }
                }
            }
              
            FinishedHandler handler = Finished;
            if(handler != null)
                handler(stopped);
        }

        IEnumerator KillTask(float killTime)
        {
            yield return null;
            if(killTime <= 0)
                yield break;
            
            yield return new WaitForSeconds(killTime);
            if (running && !paused)
                running = false;
        }

    }  
  
    static TaskManager singleton;

    public static TaskState CreateTask(IEnumerator coroutine)  
    {  
        if(singleton == null) {  
            GameObject go = new GameObject("TaskManager");  
            singleton = go.AddComponent<TaskManager>();  
        }  
        return new TaskState(coroutine);  
    }
} 